package cn.funeralobjects.dictms.service.impl;

import cn.funeralobjects.common.service.annotataion.CommonRollback;
import cn.funeralobjects.common.service.annotataion.CommonService;
import cn.funeralobjects.common.service.exception.LackDataException;
import cn.funeralobjects.common.service.exception.OperationException;
import cn.funeralobjects.common.service.validate.DataCheck;
import cn.funeralobjects.dictms.BasicInfo;
import cn.funeralobjects.dictms.entity.*;
import cn.funeralobjects.dictms.model.DictValuePathDto;
import cn.funeralobjects.dictms.repository.EDictRelatePathRepository;
import cn.funeralobjects.dictms.service.DictRelatePathService;
import cn.funeralobjects.dictms.service.DictRelateService;
import cn.funeralobjects.dictms.service.DictService;
import cn.funeralobjects.dictms.service.DictValueService;
import cn.funeralobjects.util.annotation.ArgNotZero;
import cn.funeralobjects.util.annotation.AssertArg;
import com.querydsl.jpa.JPQLQueryFactory;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @author FuneralObjects
 * Create date: 2020/6/3 1:55 AM
 */
@CommonService
public class DictRelatePathServiceImpl implements DictRelatePathService {

    @Resource
    private EDictRelatePathRepository eDictRelatePathRepository;

    @Resource
    private DictRelateService dictRelateService;
    @Resource
    private DictValueService dictValueService;
    @Resource
    private DictService dictService;
    @Resource
    private JPQLQueryFactory jpqlQueryFactory;

    @AssertArg
    @CommonRollback
    @Override
    public EDictRelatePath addBindDictValue(@ArgNotZero Integer relateId, Integer dictValueId, @ArgNotZero Integer parentId) {

        return saveBindDicValue(relateId, dictValueId, parentId, null);
    }

    @AssertArg
    @CommonRollback
    @Override
    public EDictRelatePath addBindDictValueTopDepth(@ArgNotZero Integer relateId, Integer dictValueId, Integer headValueId) {
        return saveBindDicValue(relateId, dictValueId, null, headValueId);
    }

    @AssertArg
    @CommonRollback
    @Override
    public void removeById(@ArgNotZero Integer id) {
        this.eDictRelatePathRepository.findById(id)
                .ifPresent(eDictRelatePath -> {
                    List<EDictRelatePath> deletes = eDictRelatePathRepository.findByPathLikeAndDepthIsGreaterThan(eDictRelatePath.getPath() + "%", eDictRelatePath.getDepth());
                    eDictRelatePathRepository.deleteInBatch(deletes);
                });
    }

    @AssertArg
    @Override
    public List<DictValuePathDto> findDictValuePathByRelateIdAndHeadValueId(@ArgNotZero Integer relateId, @ArgNotZero Integer headValueId) {
        return dictRelateService.findById(relateId).map(relate -> {
            return DictValuePathDto.toModel(DictValuePathDto.createSelect(jpqlQueryFactory, dictValueService.getExistsDelSalt())
                    .join(QEDict.eDict).on(QEDictValue.eDictValue.dict.id.eq(QEDict.eDict.id))
                    .where(
                            QEDictRelatePath.eDictRelatePath.relate.id.eq(relateId)
                                    .and(QEDict.eDict.delSalt.eq(dictService.getExistsDelSalt()))
                                    .and(QEDictRelatePath.eDictRelatePath.depth.eq((short) 1))
                                    .and(QEDictRelatePath.eDictRelatePath.headValue.id.eq(headValueId))
                    ).fetch());
        }).orElseGet(() -> new ArrayList<>(0));

    }

    @AssertArg
    @Override
    public List<DictValuePathDto> findDictValuePathByParentId(@ArgNotZero Integer parentId) {
        return DictValuePathDto.toModel(DictValuePathDto.createSelect(jpqlQueryFactory, dictValueService.getExistsDelSalt())
                .join(QEDict.eDict).on(QEDictValue.eDictValue.dict.id.eq(QEDict.eDict.id))
                .where(
                        QEDict.eDict.delSalt.eq(dictService.getExistsDelSalt())
                                .and(QEDictRelatePath.eDictRelatePath.parentId.eq(parentId))
                ).fetch());
    }

    private EDictRelatePath saveBindDicValue(Integer relateId, Integer dictValueId, Integer parentId, Integer headValueId) {
        EDictRelate relate = dictRelateService.findById(relateId)
                .orElseThrow(() -> new LackDataException(
                        String.format("The DictRelate[%s] doesn't exists!", relateId),
                        dictRelateService.createDataCode("id", String.valueOf(relateId))));
        EDictRelatePath parent;
        if (parentId != null) {
            parent = eDictRelatePathRepository.findById(parentId)
                    .orElseThrow(() -> new LackDataException(
                            String.format("The DictRelatePath[%s] doesn't exists!", parentId),
                            this.createDataCode("parentId", String.valueOf(parentId))));
        } else {
            DataCheck.dataMismatch(!dictValueService.existsByIdInDict(relate.getHeadDict().getId(), headValueId), () -> dictValueService.createDataCode("id", String.valueOf(headValueId)), () -> dictService.createDataCode("id", String.valueOf(relate.getHeadDict().getId())));
            parent = new EDictRelatePath();
            parent.setId(TOP_PARENT_ID);
            EDictValue head = new EDictValue();
            head.setId(headValueId);
            parent.setHeadValue(head);
            parent.setPath(String.valueOf(headValueId));
            parent.setDepth((short) 0);
        }

        Short depth = (short) (parent.getDepth() + 1);
        if (depth.compareTo(relate.getDepth()) > 0) {
            throw new OperationException(String.format("The depth[%s] cannot be bigger than %s in relate[%s]", depth, relate.getDepth(), relate.getId()), this.createDataCode("depth", String.valueOf(depth)));
        }
        Integer dictId = relate.getRule().get(depth.intValue());
        DataCheck.dataMismatch(!dictValueService.existsByIdInDict(dictId, dictValueId), () -> dictValueService.createDataCode("id", String.valueOf(dictValueId)), () -> dictService.createDataCode("id", String.valueOf(dictId)));

        EDictRelatePath entity = new EDictRelatePath();
        EDictValue dictValue = new EDictValue();
        dictValue.setId(dictValueId);
        String path = parent.getPath() + ":" + dictValueId;
        entity.setDepth(depth)
                .setDictValue(dictValue)
                .setParentId(parent.getId())
                .setHeadValue(parent.getHeadValue())
                .setRelate(relate)
                .setPath(path);
        return eDictRelatePathRepository.save(entity);
    }

    @Override
    public String getName() {
        return ENTITY_NAME;
    }

    @Override
    public String getModuleName() {
        return BasicInfo.MODULE_NAME;
    }
}
